<?php

namespace App\Services\Im;

class WechatService
{
    private $app_key;
    private $app_secret;

    private $key = false;
    private $sdkappid = 0;

    public function __construct()
    {
        $this->sdkappid = '1600058144';
        $this->key = '071f9487e1f032b3a863d6877b7409ec32eeb4ddfbe8d319ad4750bddc07aa07';
    }

    protected function getAuth() {}

    public function test()
    {
        return $this->post('im_open_login_svc/account_import', [
            'UserID' => 'user_1',
            'Nick' => 'user_1',
            'FaceUrl' => '',
        ]);
    }

    /**
     * 用户注册
     * $id ID
     */
    public function create($user, $name, $head)
    {
        return $this->post('im_open_login_svc/account_import', [
            'UserID' => $user,
            'Nick' => $name,
            'FaceUrl' => $head,
        ]);
    }

    /**
     * 获取单个用户的详细信息
     * $username  用户名
     */
    public function get($user)
    {
        return $this->post('profile/portrait_get', [
            'To_Account' => $user,
            'TagList' => [
                "Tag_Profile_IM_Nick",
                "Tag_Profile_IM_AllowType",
                "Tag_Profile_IM_Image"
            ],
        ]);
    }


    /**
     * 添加好友
     * $username  要添加好友的用户名
     * $contact   好友用户名
     */
    public function add($from_user, $to_user, $source = 'PC')
    {
        return $this->post('sns/friend_add', [
            'From_Account' => $from_user,
            'AddFriendItem' => [
                [
                    "To_Account" => $to_user,
                    "AddSource" => 'AddSource_Type_' . $source,
                ]
            ],
        ]);
    }

    /**
     * 好友列表
     * $username  要获取好友列表的用户名
     *  
     */
    public function get_friendlist($user, $page, $sequence)
    {
        return $this->post('sns/friend_get', [
            'From_Account' => $user,
            'StartIndex' => $page,
            'StandardSequence' => $sequence,
        ]);
    }

    /**
     * 获取单个用户加入的所有群组
     */
    public function get_user_groupall($username) {}


    //获取环信token
    public function hxtoken() {}

    //获取用户token
    public function get_token($user_id)
    {
        return $this->genUserSig($user_id);
    }


    /**
     * 设置用户属性
     * $username  要设置属性的用户名
     * $contact   要设置的属性（键：属性名；值：属性值）
     */
    public function attribute($user, $name, $head, $content="")
    {

        $ProfileItem = [];
        if ($name) {
            $ProfileItem[] =  [
                "Tag" => "Tag_Profile_IM_Nick",
                "Value" => $name
            ];
        }
        if ($head) {
            $ProfileItem[] =  [
                "Tag" => "Tag_Profile_IM_Image",
                "Value" => $head
            ];
        }
        
        if ($content) {
            $ProfileItem[] =  [
                "Tag" => "Tag_Profile_IM_SelfSignature",
                "Value" => $content
            ];
        }
        return $this->post('profile/portrait_set', [
            'From_Account' => $user,
            'ProfileItem' => $ProfileItem
        ]);
    }

    /**
     * 获取用户属性
     * $username  要设置属性的用户名
     * $contact   要设置的属性（键：属性名；值：属性值）
     */
    public function getMetadataFromUser($username) {}

    /* 
     type user_ | store_ | job_
     * 获取随机数
     * 云信 IM 账号，必须保证唯一性。若涉及字母，传参时请一律小写处理。只允许字母、数字、半角下划线_、@、半角点以及半角-。请注意以此接口返回结果中的accid为准。
     * 字符串长度上限 32
     */
    private function get_accid($type = 'user_', $id = 0)
    {
        return md5($type . $id);
    }


    /*获取随机数*/
    private function get_nonce($size = 128)
    {
        $nonce = "";
        $hexDigits = "0123456789abcdef";
        //随机字符串最大128个字符，也可以小于该数
        for ($i = 0; $i < $size; $i++) {
            $nonce .= $hexDigits[rand(0, 15)];
        }
        return $nonce;
    }

    /*计算并获取CheckSum*/
    private function get_check_sum($nonce, $curtime)
    {
        $app_secret = $this->app_secret;
        $check_sum = sha1($app_secret . $nonce . $curtime);
        return $check_sum;
    }
    /*生成HTTP POST提交的字段*/
    private function buildParam($data)
    {
        $ret = "";
        if (is_array($data) && count($data) > 0) {
            $arr = [];
            foreach ($data as $k => $v) {
                array_push($arr, $k . "=" . urlencode($v));
            }
            $ret = join("&", $arr);
        }
        return $ret;
    }

    private function post($url, $param)
    {
        $url_fix = 'https://console.tim.qq.com/v4/';
        $data = [
            'sdkappid' => $this->sdkappid,
            'identifier' => 'test',
            'usersig' => $this->genUserSig('test'),
            'random' => mt_rand(0, 4294967295),
            'contenttype' => 'json'
        ];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url_fix . $url . '?' . http_build_query($data));
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($param));
        curl_setopt($ch, CURLOPT_HEADER, false);
        // curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $result = curl_exec($ch);
        if ($result === false) {
            $result = curl_errno($ch);
        }
        return json_decode($result);
    }


    public function genUserSig($userid, $expire = 86400 * 180)
    {
        return $this->__genSig($userid, $expire, '', false);
    }

    public function genPrivateMapKey($userid, $expire, $roomid, $privilegeMap)
    {
        $userbuf = $this->__genUserBuf($userid, $roomid, $expire, $privilegeMap, 0, '');
        return $this->__genSig($userid, $expire, $userbuf, true);
    }
    public function genPrivateMapKeyWithStringRoomID($userid, $expire, $roomstr, $privilegeMap)
    {
        $userbuf = $this->__genUserBuf($userid, 0, $expire, $privilegeMap, 0, $roomstr);
        return $this->__genSig($userid, $expire, $userbuf, true);
    }
    private function base64_url_encode($string)
    {
        static $replace = array('+' => '*', '/' => '-', '=' => '_');
        $base64 = base64_encode($string);
        if ($base64 === false) {
            throw new \Exception('base64_encode error');
        }
        return str_replace(array_keys($replace), array_values($replace), $base64);
    }

    private function base64_url_decode($base64)
    {
        static $replace = array('+' => '*', '/' => '-', '=' => '_');
        $string = str_replace(array_values($replace), array_keys($replace), $base64);
        $result = base64_decode($string);
        if ($result == false) {
            throw new \Exception('base64_url_decode error');
        }
        return $result;
    }

    private function __genUserBuf($account, $dwAuthID, $dwExpTime, $dwPrivilegeMap, $dwAccountType, $roomStr)
    {

        //cVer  unsigned char/1 版本号，填0
        if ($roomStr == '')
            $userbuf = pack('C1', '0');
        else
            $userbuf = pack('C1', '1');

        $userbuf .= pack('n', strlen($account));
        //wAccountLen   unsigned short /2   第三方自己的帐号长度
        $userbuf .= pack('a' . strlen($account), $account);
        //buffAccount   wAccountLen 第三方自己的帐号字符
        $userbuf .= pack('N', $this->sdkappid);
        //dwSdkAppid    unsigned int/4  sdkappid
        $userbuf .= pack('N', $dwAuthID);
        //dwAuthId  unsigned int/4  群组号码/音视频房间号
        $expire = $dwExpTime + time();
        $userbuf .= pack('N', $expire);
        //dwExpTime unsigned int/4  过期时间 （当前时间 + 有效期（单位：秒，建议300秒））
        $userbuf .= pack('N', $dwPrivilegeMap);
        //dwPrivilegeMap unsigned int/4  权限位
        $userbuf .= pack('N', $dwAccountType);
        //dwAccountType  unsigned int/4
        if ($roomStr != '') {
            $userbuf .= pack('n', strlen($roomStr));
            //roomStrLen   unsigned short /2   字符串房间号长度
            $userbuf .= pack('a' . strlen($roomStr), $roomStr);
            //roomStr   roomStrLen 字符串房间号
        }
        return $userbuf;
    }

    private function hmacsha256($identifier, $curr_time, $expire, $base64_userbuf, $userbuf_enabled)
    {
        $content_to_be_signed = 'TLS.identifier:' . $identifier . "\n"
            . 'TLS.sdkappid:' . $this->sdkappid . "\n"
            . 'TLS.time:' . $curr_time . "\n"
            . 'TLS.expire:' . $expire . "\n";
        if (true == $userbuf_enabled) {
            $content_to_be_signed .= 'TLS.userbuf:' . $base64_userbuf . "\n";
        }
        return base64_encode(hash_hmac('sha256', $content_to_be_signed, $this->key, true));
    }

    private function __genSig($identifier, $expire, $userbuf, $userbuf_enabled)
    {
        $curr_time = time();
        $sig_array = array(
            'TLS.ver' => '2.0',
            'TLS.identifier' => strval($identifier),
            'TLS.sdkappid' => intval($this->sdkappid),
            'TLS.expire' => intval($expire),
            'TLS.time' => intval($curr_time)
        );

        $base64_userbuf = '';
        if (true == $userbuf_enabled) {
            $base64_userbuf = base64_encode($userbuf);
            $sig_array['TLS.userbuf'] = strval($base64_userbuf);
        }

        $sig_array['TLS.sig'] = $this->hmacsha256($identifier, $curr_time, $expire, $base64_userbuf, $userbuf_enabled);
        if ($sig_array['TLS.sig'] === false) {
            throw new \Exception('base64_encode error');
        }
        $json_str_sig = json_encode($sig_array);
        if ($json_str_sig === false) {
            throw new \Exception('json_encode error');
        }
        $compressed = gzcompress($json_str_sig);
        if ($compressed === false) {
            throw new \Exception('gzcompress error');
        }
        return $this->base64_url_encode($compressed);
    }

    private function __verifySig($sig, $identifier, &$init_time, &$expire_time, &$userbuf, &$error_msg)
    {
        try {
            $error_msg = '';
            $compressed_sig = $this->base64_url_decode($sig);
            $pre_level = error_reporting(E_ERROR);
            $uncompressed_sig = gzuncompress($compressed_sig);
            error_reporting($pre_level);
            if ($uncompressed_sig === false) {
                throw new \Exception('gzuncompress error');
            }
            $sig_doc = json_decode($uncompressed_sig);
            if ($sig_doc == false) {
                throw new \Exception('json_decode error');
            }
            $sig_doc = (array)$sig_doc;
            if ($sig_doc['TLS.identifier'] !== $identifier) {
                throw new \Exception("identifier dosen't match");
            }
            if ($sig_doc['TLS.sdkappid'] != $this->sdkappid) {
                throw new \Exception("sdkappid dosen't match");
            }
            $sig = $sig_doc['TLS.sig'];
            if ($sig == false) {
                throw new \Exception('sig field is missing');
            }

            $init_time = $sig_doc['TLS.time'];
            $expire_time = $sig_doc['TLS.expire'];

            $curr_time = time();
            if ($curr_time > $init_time + $expire_time) {
                throw new \Exception('sig expired');
            }

            $userbuf_enabled = false;
            $base64_userbuf = '';
            if (isset($sig_doc['TLS.userbuf'])) {
                $base64_userbuf = $sig_doc['TLS.userbuf'];
                $userbuf = base64_decode($base64_userbuf);
                $userbuf_enabled = true;
            }
            $sigCalculated = $this->hmacsha256($identifier, $init_time, $expire_time, $base64_userbuf, $userbuf_enabled);

            if ($sig != $sigCalculated) {
                throw new \Exception('verify failed');
            }

            return true;
        } catch (\Exception $ex) {
            $error_msg = $ex->getMessage();
            return false;
        }
    }

    public function verifySig($sig, $identifier, &$init_time, &$expire_time, &$error_msg)
    {
        $userbuf = '';
        return $this->__verifySig($sig, $identifier, $init_time, $expire_time, $userbuf, $error_msg);
    }

    public function verifySigWithUserBuf($sig, $identifier, &$init_time, &$expire_time, &$userbuf, &$error_msg)
    {
        return $this->__verifySig($sig, $identifier, $init_time, $expire_time, $userbuf, $error_msg);
    }
}
